/*  This file is part of TextEditorLibrary - a library for the programmatic manipulation of text.

    TextEditorLibrary is free software: you can redistribute it and/or modify it under the terms
    of the GNU Lesser General Public License as published by the Free Software Foundation, either
    version 3 of the License, or (at your option) any later version.

    TextEditorLibrary is distributed in the hope that it will be useful, but WITHOUT ANY 
    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
    PURPOSE.  See the GNU Lesser General Public License for more details at 
    <http://www.gnu.org/licenses/>.
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace TextEditorLibrary
{
/// <summary>Defines a buffer of text which can be manipulated programmatically.</summary>
    public partial class Text
    {
        internal const int tgtBlock = 3686;         // Aim for this size block (90% full)
        internal const int maxBlock = 4096;         // Don't go bigger than this size

        private static int _IDs = 0;                // Number of files so far
        private int _ID;                            // Text ID for debug

        internal LinkedList<StringBuilder> _text;   // The text lives in a series of StringBuilders
        private string _filename;                   // Filename for writing out again
        private Encoding _encoding;                 // Character encoding for writing out again
        internal int _length;                       // Number of characters in Text

        internal Mark _cursor;                      // Mark for current position (also in list)
        private List<Mark> _marks;                  // List of marks created by the caller

    /// <summary>Creates an empty Text.</summary>
        public Text()
        {
            _ID = _IDs++;
            _text = new LinkedList<StringBuilder>();
            _text.AddFirst(new StringBuilder(maxBlock));
            _filename = "TelOutput.txt";
            _encoding = Encoding.ASCII;
            _length = 0;
            AddMarks();
        }

    /// <summary>Creates a Text from a string.</summary>
    /// <param name="text">string value to add to the Text.</param>
        public Text(string text)
        {
            _ID = _IDs++;
            _text = new LinkedList<StringBuilder>();
            _filename = "TelOutput.txt";
            _encoding = Encoding.ASCII;
            _length = text.Length;

            int i = 0;
            do
            {
                _text.AddLast(
                    new StringBuilder(text.Substring(i,Math.Min(_length-i,tgtBlock)),maxBlock));
                i += tgtBlock;
            } while (i<_length);

            CleanCR();
            AddMarks();
        }

    /// <summary>Creates a Text from a text file.</summary>
    /// <param name="filename">string value containing the text file path.</param>
    /// <param name="encoding">Encoding value or null for default ASCII.</param>
        public Text(string filename, Encoding encoding)
        {
            _filename = filename;
            _encoding = encoding;
            if(_encoding==null) _encoding = Encoding.ASCII;
            
            StreamReader stream = new StreamReader(_filename,_encoding);
            FileFromStream(stream);
            stream.Close();
        }

    /// <summary>Creates a Text from a StreamReader.</summary>
    /// <param name="stream">StreamReader to read text from.</param>
        public Text(StreamReader stream)
        {
            _filename = "TelOutput.txt";
            _encoding = Encoding.ASCII;
            FileFromStream(stream);
        }

    //---------------------------------------------------------------------------------------------
    
    // Fill the Text from a StreamReader
        private void FileFromStream(StreamReader stream)
        {
            _ID = _IDs++;
            _text = new LinkedList<StringBuilder>();
            _length = 0;

            int i;
            char[] buffer = new char[tgtBlock];
            do
            {
                i = stream.ReadBlock(buffer, 0, tgtBlock);
                if (i>0)
                {
                    _text.AddLast(new StringBuilder(new string(buffer,0,i),maxBlock));
                    _length += i;
                }
            } while (i > 0);

            CleanCR();
            AddMarks();
        }

    // Clean out any CR/LF combinations by simply removing CRs
        private void CleanCR()
        {
            foreach (StringBuilder block in _text)
            {
                int j = 0;
                int k = block.Length;

                for (int i = 0; i<block.Length; i++)
                {
                    if (block[i] != '\r') block[j++] = block[i];
                }
                block.Length = j;
                _length -= k - j;
            }
        }

    // Add the current Mark at the beginning of the Text, and create the list of Marks
        private void AddMarks()
        {
            _cursor = new Mark(this);
            _marks = new List<Mark>();
            _marks.Add(_cursor);
        }

    //---------------------------------------------------------------------------------------------
    
    /// <summary>Writes the Text to a text file using the stored filename and encoding.</summary>
        public void Write()
        {
            StreamWriter stream = new StreamWriter(_filename,false,_encoding);
            Write(stream);
            stream.Close();
        }

    /// <summary>Writes the Text to a text file using the passed filename and encoding.</summary>
    /// <param name="filename">string value containing the text file path.</param>
    /// <param name="encoding">Encoding value or null for default ASCII.</param>
        public void Write(string filename, Encoding encoding)
        {
            _filename = filename;
            _encoding = encoding;
            if (_encoding == null) _encoding = Encoding.ASCII;
            StreamWriter stream = new StreamWriter(_filename,false,_encoding);
            Write(stream);
            stream.Close();
        }

    /// <summary>Writes the Text to a text file using the passed filename and
    /// ASCII encoding.</summary>
    /// <param name="filename">string value containing the text file path.</param>
        public void Write(string filename)
        {
            Write(filename, null);
        }

    /// <summary>Writes the Text using a StreamWriter.</summary>
    /// <param name="stream">StreamWriter to be written to.</param>
        public void Write(StreamWriter stream)
        {
            foreach (StringBuilder block in _text)
            {
                int start = 0;
                for (int end = 0; end < block.Length; end++)
                {
                    if (block[end] == '\n')
                    {
                        stream.WriteLine(block.ToString(start, end - start));
                        start = end + 1;
                    }
                }
                stream.Write(block.ToString(start, block.Length - start));
            }
        }

    //---------------------------------------------------------------------------------------------
    
    /// <summary><para>Inserts text at the current cursor position in the Text.</para>
    /// <para>The cursor position is moved to the end of the inserted text, and the Text is 
    /// returned to permit chaining of Insert calls. Other Marks originally at the cursor
    /// position are not moved.</para></summary>
    /// <param name="text">string value containing the text for insertion.</param>
        public Text Insert(string text)
        {
            _cursor.Insert(text);
            return this;
        }

    /// <summary><para>Inserts text at the current cursor position in the Text.</para>
    /// <para>The cursor position is moved to the end of the inserted text, and the Text is 
    /// returned to permit chaining of Insert calls. Other Marks originally at the cursor
    /// position are not moved.</para></summary>
    /// <param name="source">object whose ToString method will define the text for insertion.</param>
        public Text Insert(object source)
        {
            _cursor.Insert(source.ToString());
            return this;
        }

    /// <summary>Deletes text forwards or backwards from the current cursor position.</summary>
    /// <param name="chars">Number of characters to delete, negative for deletion backwards,
    /// and positive for deletion forwards.</param>
        public void Delete(int chars)
        {
            _cursor.Delete(chars);
        }

    //---------------------------------------------------------------------------------------------

    // Split a block at the given position; we may be at the end of the block in which case an
    // empty block is created, but a subsequent Join will always remove it again
        internal void Split(LinkedListNode<StringBuilder> node, int position)
        {
            StringBuilder oldBlock = node.Value;
            StringBuilder newBlock = new StringBuilder(maxBlock);

        // Create new block, copy stuff over, shorten the old block
            newBlock.Length = oldBlock.Length - position;
            for (int i=position, j=0; i<oldBlock.Length; i++, j++) newBlock[j] = oldBlock[i];
            oldBlock.Length = position;

        // Insert the new block into the list
            node.List.AddAfter(node, newBlock);

        // Look through all Marks in the Text moving those that are in the node after position
            foreach (Mark mark in _marks)
            {
                if (mark._node==node && mark._position>position)
                {
                    mark._node = node.Next;
                    mark._position -= position;
                }
            }

        }

    // Join a block with the next one if it exists and if the lengths add up to no more than max
        internal void Join(LinkedListNode<StringBuilder> node)
        {
        // Skip if no next node
            if (node.Next==null) return;
            StringBuilder oldBlock = node.Value;
            StringBuilder newBlock = node.Next.Value;

        // Skip if the two blocks are too long
            int oldLength = oldBlock.Length;
            if (oldLength+newBlock.Length>maxBlock) return;

        // Extend the first block and copy over all the text
            oldBlock.Length += newBlock.Length;
            for (int i=oldLength, j=0; j<newBlock.Length; i++, j++) oldBlock[i] = newBlock[j];

        // Look through all Marks in the Text moving those that were in the second node
            foreach (Mark mark in _marks)
            {
                if (mark._node==node.Next)
                {
                    mark._node = node;
                    mark._position += oldLength;
                }
            }

            node.List.Remove(node.Next);
        }

    // Pull the Marks in a Range back to the start of the Range (before deleting it)
        internal void Pull(Mark mark1, Mark mark2)
        {
        // Look through the blocks in the Range, and find any marks in those blocks - more
        // efficient than the other way around with a large Text
            LinkedListNode<StringBuilder> node = mark1._node;
            int position = mark1._position;

        // Whole blocks
            while (node != mark2._node)
            {
                foreach (Mark mark in _marks)
                {
                    if (mark._node == node && mark._position > position)
                    {
                        mark._node = mark1._node;
                        mark._position = mark1._position;
                    }
                }

                node = node.Next;
                position = 0;
            }

        // Last block
            foreach (Mark mark in _marks)
            {
                if (mark._node == node && mark._position > position &&
                                            mark._position <= mark2._position)
                {
                    mark._node = mark1._node;
                    mark._position = mark1._position;
                }
            }
        }

    //---------------------------------------------------------------------------------------------
    
    /// <summary>Creates a new Mark in the Text using the passed Mark value.</summary>
    /// <param name="mark">Mark value representing the initial position of the new Mark.</param>
        public Mark AddMark(Mark mark)
        {
            if (this != mark._text)
               throw new ArgumentException("Passed Mark argument is in a different Text.", "mark");
            Mark mark2 = new Mark(mark);
            mark2._ID = ++Mark._IDs;
            _marks.Add(mark2);
            return mark2;
        }

    /// <summary>Creates a new Mark in the Text at the current cursor position.</summary>
        public Mark AddMark()
        {
            return AddMark(_cursor);
        }

    //---------------------------------------------------------------------------------------------
    
    /// <summary>Creates a new Range in the Text using start and end Mark values.</summary>
    /// <param name="mark1">Mark value representing the start of the new Range.</param>
    /// <param name="mark2">Mark value representing the end of the new Range.</param>
        public Range AddRange(Mark mark1, Mark mark2)
        {
            if (this != mark1._text)
              throw new ArgumentException("Passed Mark argument is in a different Text.", "mark1");
            if (this != mark2._text)
              throw new ArgumentException("Passed Mark argument is in a different Text.", "mark2");
            if (mark2-mark1<0)
              throw new ArgumentException("Passed Mark arguments are in the wrong order.",
                                                                                  "mark1, mark2");

            Mark mark1New = new Mark(mark1);
            Mark mark2New = new Mark(mark2);

            Range range = new Range(mark1New, mark2New);
            range._ID = ++Range._IDs;
            _marks.Add(mark1New);
            _marks.Add(mark2New);
            return range;
        }

    /// <summary>Creates a new Range in the Text using the passed Range value.</summary>
    /// <param name="range">Range value representing the initial span of the new Range.</param>
        public Range AddRange(Range range)
        {
            if (this != range._mark1._text)
             throw new ArgumentException("Passed Range argument is in a different Text.", "range");
            return AddRange(range._mark1, range._mark2);
        }

    /// <summary>Creates a new Range in the Text using an offset from the current cursor position.
    /// </summary>
    /// <param name="offset"><para>Int offset (positive or negative) from the current cursor 
    /// position.</para><para>The range stretches between the current cursor position and the
    /// offset position.</para></param>
        public Range AddRange(int offset)
        {
            if(offset>=0)
            {
                return AddRange(_cursor, _cursor+offset);
            } else {
                return AddRange(_cursor+offset, _cursor);
            }
        }

    /// <summary>Creates a new Range in the Text initially containing the whole Text.</summary>
        public Range AddRange()
        {
            return AddRange(Start, End);
        }

    //---------------------------------------------------------------------------------------------
    
    /// <summary><para>Removes a Mark from the list of Marks attached to the Text.</para>
    /// <para>The Mark will be invalidated by subsequent changes to the text.</para></summary>
    /// <param name="mark">Mark to be removed from the Text.</param>
        public void Remove(Mark mark)
        {
            mark._ID = 0;
            try
            {
                _marks.Remove(mark);
            }
            catch
            {
            }
        }

    /// <summary><para>Removes a Range from the list of Range attached to the Text.</para>
    /// <para>The Range will be invalidated by subsequent changes to the text.</para></summary>
    /// <param name="range">Range to be removed from the Text.</param>
        public void Remove(Range range)
        {
            range._ID = 0;
            try
            {
                _marks.Remove(range._mark1);
            }
            catch
            {
            }
            try
            {
                _marks.Remove(range._mark2);
            }
            catch
            {
            }
        }

    //---------------------------------------------------------------------------------------------
    
    /// <summary>Moves the current cursor position forwards or backwards.</summary>
    /// <param name="offset">Number of characters to move, positive or negative.</param>
        public void MoveTo(int offset)
        {
            _cursor.MoveTo(offset);
        }

    /// <summary>Moves the current cursor position to a passed Mark position.</summary>
    /// <param name="mark">Mark position to move to.</param>
        public void MoveTo(Mark mark)
        {
            _cursor.MoveTo(mark);
        }

    /// <summary>Moves the current cursor position to the beginning of the next line if there is 
    /// one, and returns true, or leaves the cursor unmoved and returns false.</summary>
        public bool NextLine()
        {
            Mark end = LineEnd();
            if (end._node==_text.Last && end._position==_text.Last.Value.Length) return false;
            MoveTo(end+1);
            return true;
        }

    /// <summary>Moves the current cursor position to the beginning of the previous line if there
    /// is one, and returns true, or leaves the cursor unmoved and returns false.</summary>
        public bool PreviousLine()
        {
            Mark start = LineStart();
            if (start._node==_text.First && start._position==0) return false;
            MoveTo(start-1);
            MoveTo(LineStart());
            return true;
        }
    //---------------------------------------------------------------------------------------------
    
    /// <summary>Converts the Text contents to a string with newline characters at line ends.
    /// </summary>
        public override string ToString()
        {
            StringBuilder text = new StringBuilder(_length);
            foreach (StringBuilder block in _text) text.Append(block);
            return text.ToString();
        }

    /// <summary><para>Returns a Mark at the start of the current line in the Text.</para>
    /// <para>Do not assign this value to a Mark variable - it will become invalidated if the
    /// text changes.  Use Text.AddMark() or Mark.MoveTo() instead.</para></summary>
        public Mark LineStart()
        {
            return _cursor.LineStart();
        }

    /// <summary><para>Returns a Mark at the end of the current line in the Text.</para>
    /// <para>Do not assign this value to a Mark variable - it will become invalidated if the
    /// text changes.  Use Text.AddMark() or Mark.MoveTo() instead.</para></summary>
        public Mark LineEnd()
        {
            return _cursor.LineEnd();
        }

    /// <summary><para>Returns a Range containing the line (excluding line feeds) which the current
    /// cursor is in.</para>
    /// <para>Do not assign this value to a Range variable - it will become invalidated if the 
    /// text changes.  Use Text.AddRange() or Range.MoveTo() instead.</para></summary>
        public Range Line()
        {
            return new Range(_cursor.LineStart(), _cursor.LineEnd());
        }

    //---------------------------------------------------------------------------------------------
    
    /// <summary>Char at the given index in the Text.</summary>
    /// <param name="index">int offset from the start of the Text.</param>
        public char this[int index]
        {
            get
            {
                if (index<0)
                    throw new ArgumentOutOfRangeException("index", "Index may not be negative.");
                if (index>=_length)
                    throw new ArgumentOutOfRangeException("index",
                                                               "Index must be less than Length.");
                return All[index];
            }

            set
            {
                if (index<0)
                    throw new ArgumentOutOfRangeException("index", "Index may not be negative.");
                if (index>=_length)
                    throw new ArgumentOutOfRangeException("index",
                                                              "Index must be less than Length.");
                All[index] = value;
            }
        }

    /// <summary>Length of the text in the Text including newline characters.</summary>
        public int Length
        {
            get { return _length; }
        }

    /// <summary><para>Current cursor position in the Text.</para>
    /// <para>Do not assign this value to a Mark variable - it will become invalidated if the
    /// text changes.  Use Text.AddMark() or Mark.MoveTo() instead.</para></summary>
        public Mark Cursor
        {
            get { return new Mark(_cursor); }
        }
    
    /// <summary><para>Position at the start of the Text.</para>
    /// <para>Do not assign this value to a Mark variable - it will become invalidated if the
    /// text changes.  Use Text.AddMark() or Mark.MoveTo() instead.</para></summary>
        public Mark Start
        {
            get { return new Mark(this); }
        }

    /// <summary><para>Position at the end of the Text.</para>
    /// <para>Do not assign this value to a Mark variable - it will become invalidated if the
    /// text changes.  Use Text.AddMark() or Mark.MoveTo() instead.</para></summary>
        public Mark End
        {
            get { return new Mark(this, _text.Last, _text.Last.Value.Length); }
        }

    /// <summary><para>Range containing the whole contents of the Text.</para>
    /// <para>Do not assign this value to a Range variable - it will become invalidated if the
    /// text changes.  Use Text.AddRange() or Range.MoveTo() instead.</para>
    /// </summary>
        public Range All
        {
            get { return new Range(Start, End); }
        }

    /// <summary>Override for default GetHashCode() - returns a unique index.</summary>
        public override int GetHashCode()
        {
            return _ID;
        }
    }

//=================================================================================================

/// <summary>Defines a bookmark in a Text which moves with the text.</summary>
    public partial class Mark
    {
        internal static int _IDs = 0;                   // Number of registered marks so far
        internal int _ID;                               // Mark ID for debug

        internal Text _text;                            // Text that the Mark is in
        internal LinkedListNode<StringBuilder> _node;   // Node that the Mark is in
        internal int _position;                         // Offset from the start of the Block
        Pattern _pattern = null;                        // Saved search pattern


    // Can't construct from the API - use Text.AddMark()
        internal Mark(Text text)
        {
            _ID = 0;
            _text = text;
            _node = text._text.First;
            _position = 0;
        }
        internal Mark(Mark mark)
        {
            _ID = 0;
            _text = mark._text;
            _node = mark._node;
            _position = mark._position;
        }
        internal Mark(Text text, LinkedListNode<StringBuilder> node, int position)
        {
            _ID = 0;
            _text = text;
            _node = node;
            _position = position;
        }

    //---------------------------------------------------------------------------------------------

    /// <summary>Moves the Mark forwards or backwards in the Text.</summary>
    /// <param name="offset">Number of characters, positive or negative, to move.</param>
        public void MoveTo(int offset)
        {
            Mark mark = this + offset;
            _node = mark._node;
            _position = mark._position;
            _pattern = null;
        }

    /// <summary>Moves the Mark to the position of a given mark value.</summary>
    /// <param name="mark">Mark value defining the new position.</param>
        public void MoveTo(Mark mark)
        {
            if (_text != mark._text)
               throw new ArgumentException("Passed Mark argument is in a different Text.", "mark");
            _node = mark._node;
            _position = mark._position;
            _pattern = null;
        }

    /// <summary>Moves the Mark to the current cursor position in its Text.</summary>
        public void MoveTo()
        {
            _node = _text._cursor._node;
            _position = _text._cursor._position;
            _pattern = null;
        }
    
    //---------------------------------------------------------------------------------------------
    
    /// <summary><para>Inserts text at the Mark position.</para>
    /// <para>The Mark is moved to the end of the inserted text, and it is returned to allow 
    /// chaining of Insert calls. Other Marks originally at the same position are not 
    /// moved.</para></summary>
    /// <param name="text">string value containing the text for insertion.</param>
        public Mark Insert(string text)
        {
            LinkedListNode<StringBuilder> node = _node;
            int position = _position;
           
        // Chop the current block at the insertion point
            _text.Split(node, position);

        // Copy in as much text as will fit in the first block
            StringBuilder block = node.Value;
            block.Length = Text.maxBlock;

            int i = 0;
            int j = position;
            for (; i<text.Length && j<Text.maxBlock; i++, j++) block[j] = text[i];

        // Add new blocks filling them up until we run out of text
            while (i<text.Length)
            {
                block = new StringBuilder(Text.maxBlock);
                block.Length = Text.tgtBlock;
                node.List.AddAfter(node, block);
                node = node.Next;

                j = 0;
                for( ; i<text.Length && j<Text.tgtBlock ; i++, j++) block[j] = text[i];
            }

        // Shorten the last block again
            block.Length = j;

        // Move the mark to the end (all other marks stay where they are)
            _node = node;
            _position = j;

        // See if the last node can squeeze in with the one after (if any)
            _text.Join(node);
            _text._length += text.Length;

            return this;
        }

    /// <summary><para>Inserts text at the Mark position.</para>
    /// <para>The Mark is moved to the end of the inserted text, and it is returned to allow 
    /// chaining of Insert calls. Other Marks originally at the same position are not 
    /// moved.</para></summary>
    /// <param name="source">object whose ToString method will define the text for insertion.</param>
        public Mark Insert(object source)
        {
            Insert(source.ToString());
            return this;
        }

    /// <summary>Deletes text forwards or backwards from the Mark position.</summary>
    /// <param name="chars">Number of characters to delete, negative for deletion backwards,
    /// and positive for deletion forwards.</param>
        public void Delete(int chars)
        {
            if (chars>0)
            {
                (new Range(this, this+chars)).Delete();
                _pattern = null;
            }
            else if (chars<0)
            {
                (new Range(this+chars, this)).Delete();
            }
        }

    //---------------------------------------------------------------------------------------------

    /// <summary>Returns the offset of this Mark from the passed Mark (positive if this Mark is
    /// after the passed Mark).</summary>
    /// <param name="mark">Mark value to compare this Mark to.</param>
        public int Offset(Mark mark)
        {
            if (mark._text != _text)
               throw new ArgumentException("Passed Mark argument is in a different Text.", "mark");

           if (_node == mark._node)
               return (_position - mark._position);

            // Search in both directions together assuming that the marks are likely to be close 
            // to each other but could be in either order
            LinkedListNode<StringBuilder> nodeb = _node.Previous;
            int offsetb = _position;
            LinkedListNode<StringBuilder> nodea = _node.Next;
            int offseta = _position - _node.Value.Length;

            do
            {
                if (nodeb != null)
                {
                    offsetb += nodeb.Value.Length;
                    if (nodeb == mark._node)
                        return offsetb - mark._position;
                    nodeb = nodeb.Previous;
                }

                if (nodea != null)
                {
                    if (nodea == mark._node)
                        return offseta - mark._position;
                    else
                        offseta -= nodea.Value.Length;
                    nodea = nodea.Next;
                }
            } while (true);
        }

    /// <summary>Returns the offset of the first Mark from the second Mark.</summary>
    /// <param name="mark1">Mark value whose offset is returned.</param>
    /// <param name="mark2">Mark value used as the base for the offset.</param>
        public static int operator -(Mark mark1, Mark mark2)
        {
            return mark1.Offset(mark2);
        }

    /// <summary>Returns the offset of this Mark from the start of its Text.</summary>
        // Difficult to make this super-efficient
        public int Offset()
        {
            int offset = _position;
            LinkedListNode<StringBuilder> node = _text._text.First;
            while (node != _node)
            {
                offset += node.Value.Length;
                node = node.Next;
            }
            return offset;
        }

    /// <summary>Returns true if the passed Mark is at the same location as this Mark.</summary>
    /// <param name="mark">Mark to compare against.</param>
        public bool SameAs(Mark mark)
        {
            if(mark._text != _text)
                throw new ArgumentException("Passed Mark argument is in a different Text.", "mark");
            return (mark._node==_node && mark._position==_position);
        }

    //---------------------------------------------------------------------------------------------
   
    /// <summary><para>Returns an unattached Mark whose position is an offset from the Mark.</para>
    /// <para>Do not assign this value to a Mark variable - it will become invalidated if the
    /// text changes.  Use Text.AddMark() or Mark.MoveTo() instead.</para></summary>
    /// <param name="mark">Mark to start from.</param>
    /// <param name="offset">Number of characters, positive or negative, of offset.</param>
        public static Mark operator + (Mark mark, int offset)
        {
            LinkedListNode<StringBuilder> node = mark._node;
            int position = mark._position;

            if (offset>0)
            {
                // Note that we cannot end up at position = 0 unless there only is one empty block
                while (position + offset > node.Value.Length && node.Next != null)
                {
                    offset -= node.Value.Length - position;
                    node = node.Next;
                    position = 0;
                }
                if (position+offset<=node.Value.Length) position += offset;
                else position = node.Value.Length;
            }

            else if (offset<0)
            {
                // Note that we cannot end up at position = 0 unless we are in the first block
                while (position + offset <= 0 && node.Previous != null)
                {
                    offset += position;
                    node = node.Previous;
                    position = node.Value.Length;
                }
                if (position+offset > 0) position += offset;
                else position = 0;
            }

            return new Mark(mark._text, node, position);
        }

    /// <summary><para>Returns an unattached Mark whose position is an offset from the Mark.</para>
    /// <para>Do not assign this value to a Mark variable - it will become invalidated if the
    /// text changes.  Use Text.AddMark() or Mark.MoveTo() instead.</para></summary>
    /// <param name="mark">Mark to start from.</param>
    /// <param name="offset">Number of characters, positive or negative, of offset.</param>
        public static Mark operator - (Mark mark, int offset)
        {
            return mark + (-offset);
        }

    /// <summary><para>Returns an unattached Range between the two Marks.</para>
    /// <para>Do not assign this value to a Range variable - it will become invalidated if the
    /// text changes.  Use Text.AddRange() or Range.MoveTo() instead.</para></summary>
    /// <param name="mark1">Mark at the start of the Range.</param>
    /// <param name="mark2">Mark at the end of the Range.</param>
        public static Range operator + ( Mark mark1, Mark mark2)
        {
            if (mark1._text != mark2._text)
              throw new ArgumentException("Passed Mark arguments are in different Files.",
                                                                                "mark1, mark2");
            if (mark2-mark1 < 0)
              throw new ArgumentException("Passed Mark arguments are in the wrong order.",
                                                                                  "mark1, mark2");
            return new Range(mark1, mark2);
        }

    /// <summary><para>Returns an unattached Mark located at the start of the line which the Mark 
    /// is in.</para>
    /// <para>Do not assign this value to a Mark variable - it will become invalidated if the
    /// text changes.  Use Text.AddMark() or Mark.MoveTo() instead.</para></summary>
        public Mark LineStart()
        {
            LinkedListNode<StringBuilder> nodePrev = _node;
            LinkedListNode<StringBuilder> node;
            int position = _position;

            do
            {
                node = nodePrev;

                while (position>0)
                {
                    if (node.Value[position-1] == '\n') return new Mark(_text, node, position);
                    position--;
                }

                nodePrev = node.Previous;
                if (nodePrev != null) position = nodePrev.Value.Length;
            } while (nodePrev != null);

            return new Mark(_text, node, 0);
        }

    /// <summary><para>Returns an unattached Mark located at the end of the line which the Mark is
    /// in.</para>
    /// <para>Do not assign this value to a Mark variable - it will become invalidated if the
    /// text changes.  Use Text.AddMark() or Mark.MoveTo() instead.</para></summary>
        public Mark LineEnd()
        {
            LinkedListNode<StringBuilder> nodeNext = _node;
            LinkedListNode<StringBuilder> node;
            int position = _position;
            int length;

            do
            {
                node = nodeNext;
                length = node.Value.Length;

                while (position<length)
                {
                    if (node.Value[position] == '\n') return new Mark(_text, node, position);
                    position++;
                }

                nodeNext = node.Next;
                if (nodeNext != null && nodeNext.Value[0] == '\n')
                    return new Mark(_text, node, position);
                position = 0;
            } while (nodeNext != null);

            return new Mark(_text, node, length);
        }

    /// <summary><para>Returns an unattached Range containing the line (excluding newline
    /// characters) which the Mark is in.</para>
    /// <para>Do not assign this value to a Range variable - it will become invalidated if the
    /// text changes.  Use Text.AddRange() or Range.MoveTo() instead.</para></summary>
        public Range Line()
        {
            return new Range(LineStart(), LineEnd());
        }

    /// <summary>Returns a string showing up to four characters either side of the Mark.</summary>
        public override string ToString()
        {
            return ToString(4);
        }

    /// <summary>Returns a string showing characters either side of the Mark.</summary>
    /// <param name="chars">Number of characters to show on either side.</param>
        public string ToString(int chars)
        {
            return String.Concat(((this-chars)+this).ToString(),
                                 "<|>",
                                 (this+(this+chars)).ToString());
        }

    //---------------------------------------------------------------------------------------------

    /// <summary>Text which the Mark is in.</summary>
        public Text Text
        {
            get {return _text;}
        }

    // Used by search to get the character after the mark if there is one, and move the mark on
        internal bool NextChar(ref char next)
        {
            if (_position < _node.Value.Length)
            {
                next = _node.Value[_position];
                _position++;
                return true;
            }
            else if (_node.Next != null)
            {
                _node = _node.Next;
                next = _node.Value[0];
                _position = 1;
                return true;
            }
            else
            {
                return false;
            }
        }

    // Used by search to get the character before the mark if there is one, and move the mark back
        internal bool PreviousChar(ref char previous)
        {
            if (_position > 0)
            {
                _position--;
                previous = _node.Value[_position];
                return true;
            }
            else if (_node.Previous != null)
            {
                _node = _node.Previous;
                _position = _node.Value.Length - 1;
                previous = _node.Value[_position];
                return true;
            }
            else
            {
                return false;
            }
        }

    /// <summary>Override for default GetHashCode() - returns an index which is unique for marks
    /// created with Text.AddMark(). Unattached Marks return a value of zero.</summary>
        public override int GetHashCode()
        {
            return _ID;
        }
    }

//=================================================================================================

/// <summary>Defines a span between two Marks in a Text which move with the text.</summary>
    public partial class Range
    {
        internal static int _IDs = 0;               // Number of registered ranges so far
        internal int _ID;                           // Range ID for debug

        internal Mark _mark1;   // Start mark
        internal Mark _mark2;   // End mark

    // Can't construct from the API - use Text.AddRange()
        internal Range(Mark mark1, Mark mark2)
        {
            _ID = 0;
            _mark1 = mark1;
            _mark2 = mark2;
        }

    //---------------------------------------------------------------------------------------------
    
    /// <summary>Moves the Range to the span of a given range value.</summary>
    /// <param name="range">Range value defining the new span.</param>
        public void MoveTo(Range range)
        {
            if (range._mark1._text != _mark1._text)
             throw new ArgumentException("Passed Range argument is in a different Text.", "range");
            _mark1._node = range._mark1._node;
            _mark1._position = range._mark1._position;
            _mark2._node = range._mark2._node;
            _mark2._position = range._mark2._position;
        }

    /// <summary>Moves the Range to lie between the given marks.</summary>
    /// <param name="mark1">Mark value defining the start of the new span.</param>
    /// <param name="mark2">Mark value defining the end of the new span.</param>
        public void MoveTo(Mark mark1, Mark mark2)
        {
            if (mark1._text != _mark1._text)
              throw new ArgumentException("Passed Mark argument is in a different Text.", "mark1");
            if (mark2._text != _mark1._text)
              throw new ArgumentException("Passed Mark argument is in a different Text.", "mark2");
            if (mark2-mark1 < 0)
                    throw new ArgumentException("Passed Mark arguments are in the wrong order.",
                                                                                  "mark1, mark2");
            
            _mark1._node = mark1._node;
            _mark1._position = mark1._position;
            _mark2._node = mark2._node;
            _mark2._position = mark2._position;
        }

    //---------------------------------------------------------------------------------------------
    
    /// <summary><para>Deletes the text in the Range.</para>
    /// <para>All marks in the Range move to the start (and end) of the Range.</para></summary>
        public void Delete()
        {
        // Unregistered copy of end mark so that it will not get moved by Pull if registered
            Mark mark2 = new Mark(_mark2);

        // Move all marks inside the range back to the start
            _mark1._text.Pull(_mark1, mark2);

        // Split the last block at the end of the range - mark stays with last bit to be deleted
            mark2._text.Split(mark2._node, mark2._position);

        // If the range lies in one block, then just truncate the block
            if (mark2._node == _mark1._node)
            {
                _mark1._text._length -= _mark1._node.Value.Length - _mark1._position;
                _mark1._node.Value.Length = _mark1._position;
            }

        // Otherwise we are safe to split the first block at the start of the range - mark stays
        // with the last block to keep
            else
            {
                _mark1._text.Split(_mark1._node, _mark1._position);

            // Whip out the blocks in between
                LinkedListNode<StringBuilder> nodeAfter = mark2._node.Next;
                LinkedListNode<StringBuilder> node = _mark1._node.Next;
                do
                {
                    _mark1._text._length -= node.Value.Length;
                    node.List.Remove(node);
                    node = _mark1._node.Next;
                } while (node != nodeAfter);
            }

        // Move the last mark to the first in case it is registered
            _mark2._node = _mark1._node;
            _mark2._position = _mark1._position;

        // See if we can stick the bits back together
            _mark1._text.Join(_mark1._node);
        }

    /// <summary><para>Replaces the text in the Range with a given string.</para>
    /// <para>All marks initially in the Range end up at the start of the Range.</para></summary>
    /// <param name="text">string value containing the text for insertion.</param>
        public void Replace(string text)
        {
            Delete();
            _mark2.Insert(text);
        }

    //---------------------------------------------------------------------------------------------
    
    /// <summary>Converts the Range contents to a string with newline characters at line ends.
    /// </summary>
        public override string ToString()
        {
            int length = Length();
            StringBuilder text = new StringBuilder(length);
            text.Length = length;

            LinkedListNode<StringBuilder> node = _mark1._node;
            int position = _mark1._position;
            int i = 0;

            while (node != _mark2._node)
            {
                for (int j=position; j<node.Value.Length; j++, i++) text[i] = node.Value[j];
                position = 0;
                node = node.Next;
            }
            for (int j=position; j<_mark2._position; j++, i++) text[i] = node.Value[j];

            return text.ToString();
        }

    //---------------------------------------------------------------------------------------------

    /// <summary>Returns the length of the Range, including newline characters.</summary>
        public int Length()
        {
            int length = 0;
            int start = _mark1._position;
            LinkedListNode<StringBuilder> node = _mark1._node;

            while (node != _mark2._node)
            {
                length += node.Value.Length - start;
                start = 0;
                node = node.Next;
            }
            length += _mark2._position - start;
            return length;
        }

    /// <summary>Returns true if the passed Range has the same extent as this Range.</summary>
    /// <param name="range">Range to compare against.</param>
        public bool SameAs(Range range)
        {
            if(range._mark1._text != _mark1._text)
                throw new ArgumentException("Passed Range argument is in a different Text.", "range");
            return (range._mark1._node==_mark1._node && range._mark1._position==_mark1._position &&
                    range._mark2._node==_mark2._node && range._mark2._position==_mark2._position);
        }

    //---------------------------------------------------------------------------------------------

    /// <summary>Char at the given index in the Range.</summary>
    /// <param name="index">int offset from the start of the Range.</param>
        public char this[int index]
        {
            get
            {
                if (index<0)
                    throw new ArgumentOutOfRangeException("index", "Index may not be negative.");
                if (index>=Length())
                    throw new ArgumentOutOfRangeException("index",
                                                              "Index must be less than Length.");
                Mark mark = new Mark(_mark1+index);

            // Look out! If we are at the end of the block we have to go to the next one.
                if (mark._position==mark._node.Value.Length) return mark._node.Next.Value[0];
                else return mark._node.Value[mark._position];
            }

            set
            {
                if (index<0)
                    throw new ArgumentOutOfRangeException("index", "Index may not be negative.");
                if (index>=Length())
                    throw new ArgumentOutOfRangeException("index",
                                                              "Index must be less than Length.");
                Mark mark = new Mark(_mark1+index);

            // Look out! If we are at the end of the block we have to go to the next one.
                if (mark._position==mark._node.Value.Length) mark._node.Next.Value[0] = value;
                else mark._node.Value[mark._position] = value;
            }
        }

    /// <summary><para>Position at the start of the Range.</para>
    /// <para>Do not assign this value to a Mark variable - it will become invalidated if the
    /// text changes.  Use Text.AddMark() or Mark.MoveTo() instead.</para></summary>
        public Mark Start
        {
            get { return new Mark(_mark1); }
        }

    /// <summary><para>Position at the end of the Range.</para>
    /// <para>Do not assign this value to a Mark variable - it will become invalidated if the
    /// text changes.  Use Text.AddMark() or Mark.MoveTo() instead.</para></summary>
        public Mark End
        {
            get { return new Mark(_mark2); }
        }
 
    /// <summary>Text which the Range is in.</summary>
        public Text Text
        {
            get { return _mark1._text; }
        }

    /// <summary>Override for default GetHashCode() - returns an index which is unique for ranges
    /// created with Text.AddRange(). Unattached Ranges return a value of zero.</summary>
        public override int GetHashCode()
        {
            return _ID;
        }
    }

//=================================================================================================

}
